package cn.stylefeng.guns.modular.time.tasks;

import cn.hutool.core.util.StrUtil;
import cn.stylefeng.guns.core.utils.JPushUtil;
import cn.stylefeng.guns.modular.time.entity.AppUser;
import cn.stylefeng.guns.modular.time.service.AppUserService;
import cn.stylefeng.guns.modular.time.service.NotificationService;
import cn.stylefeng.roses.kernel.timer.api.TimerAction;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import jakarta.annotation.Resource;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class NewsTimer implements TimerAction {

    private static final Logger log = LoggerFactory.getLogger(NewsTimer.class);

    @Resource
    private JPushUtil jPushUtil;

    @Resource
    private AppUserService appUserService;
    @Resource
    private NotificationService notificationService;

    @Override
    public void action(String params) {
        String flag = StrUtil.trimToEmpty(params);
        if ("daily_ai_news".equalsIgnoreCase(flag) || "daily".equalsIgnoreCase(flag)) {
            crawlAndPushDailyAiNews();
            return;
        }
        System.out.println(StrUtil.format("这是一个定时任务测试的程序，一直输出这行内容！这个是参数: {}", params));

        String alert = "定时任务提醒：" + params;
        Map<String, Object> extras = new HashMap<>();
        extras.put("type", "news_timer");
        extras.put("params", params);

        // 查询所有已绑定 RegistrationID 的用户，并逐个推送
        List<AppUser> users = appUserService.list(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getDelFlag, "N")
                .isNotNull(AppUser::getRegistrationId));

        int successCount = 0;
        int failCount = 0;
        for (AppUser user : users) {
            String rid = user.getRegistrationId();
            if (rid == null || rid.isBlank()) { continue; }
            JPushUtil.PushResult result = jPushUtil.pushNotificationToRegistrationId(rid, alert, extras, "任务通知");
            if (result.isSuccess()) {
                successCount++;
                log.info("JPush 通知推送成功: userId={}, rid={}, status={}, body={}", user.getUserId(), rid, result.getStatus(), result.getResponseBody());
            } else {
                failCount++;
                log.warn("JPush 通知推送失败: userId={}, rid={}, status={}, error={}", user.getUserId(), rid, result.getStatus(), result.getError());
            }
        }

        // 记录汇总，便于观察效果
        log.info("定时任务推送完成：成功 {} 条，失败 {} 条，共 {} 条", successCount, failCount, users.size());
    }

    /**
     * 抓取每日AI资讯（仅当日）并按极光推送格式推送给所有绑定 RegistrationID 的用户
     */
    public void crawlAndPushDailyAiNews() {
        String todayContent = null;
        java.util.List<NewsItem> items = null;
        try {
            todayContent = fetchTodayNewsContent("https://ai-bot.cn/daily-ai-news/");
            items = extractTodayNewsItemsFromUrl("https://ai-bot.cn/daily-ai-news/");
        } catch (Exception e) {
            log.error("抓取每日AI资讯失败", e);
        }
        if (StrUtil.isBlank(todayContent)) {
            log.warn("今日AI资讯为空或未找到当日内容");
            return;
        }
        String pageDate = null;
        try { pageDate = extractLatestDateLabelFromUrl("https://ai-bot.cn/daily-ai-news/"); } catch (Exception ignored) {}
        String dateLabel = StrUtil.isBlank(pageDate) ? buildTodayLabel() : pageDate;
        String alert = "每日AI资讯（" + dateLabel + "): " + summarizeForAlert(todayContent);
        Map<String, Object> extras = new HashMap<>();
        extras.put("type", "daily_ai_news");
        extras.put("date", dateLabel);
        String details = buildDetailsJsonFromItems(dateLabel, items);
        // 推送时不携带大型内容，避免极光Payload过大导致推送失败；details 仅用于入库
        pushToUsersDedup(alert, extras, "每日AI资讯", dateLabel, todayContent, details);
    }

    /**
     * 拉取页面并提取当日新闻文本内容（纯文本）
     */
    /**
     * 拉取页面并按新闻列表的 DOM 结构解析今日内容
     */
    private String fetchTodayNewsContent(String url) throws Exception {
        String html = httpGet(url);
        if (StrUtil.isBlank(html)) return null;
        String section = extractTodayNewsFromHtml(html);
        if (!StrUtil.isBlank(section)) return section;
        String pre = html
                .replaceAll("(?i)<br\\s*/?>", "\n")
                .replaceAll("(?i)</p>", "\n")
                .replaceAll("(?i)</li>", "\n")
                .replaceAll("(?i)</h[1-6]>", "\n");
        String text = pre.replaceAll("<[^>]+>", "");
        String today = buildTodayLabel();
        int start = indexOfToday(text, today);
        if (start < 0) return null;
        int end = indexOfNextDate(text, start + today.length());
        String fallback = (end > start) ? text.substring(start, end) : text.substring(start);
        fallback = fallback.replaceAll("\\n{2,}", "\n").trim();
        int firstBreak = fallback.indexOf('\n');
        if (firstBreak > 0) fallback = fallback.substring(firstBreak + 1).trim();
        return fallback;
    }

    /**
     * 解析 HTML，按 <div class="news-list"> 分块，匹配今日的 <div class="news-date">，
     * 并提取该块内所有 <div class="news-item"> 的标题与描述为纯文本
     */
    private String extractTodayNewsFromHtml(String html) {
        String todayPrefix = buildTodayLabel();
        Pattern blockStart = Pattern.compile("(?is)<div\\s+class=\"news-list\">\\s*");
        Matcher m = blockStart.matcher(html);
        int[] starts = new int[256];
        int count = 0;
        while (m.find()) {
            if (count == starts.length) break;
            starts[count++] = m.start();
        }
        if (count == 0) return null;
        String firstOut = null;
        for (int i = 0; i < count; i++) {
            int startIdx = starts[i];
            int endIdx = (i + 1 < count) ? starts[i + 1] : html.length();
            String block = html.substring(startIdx, endIdx);
            Matcher dm = Pattern.compile("(?is)<div\\s+class=\"news-date\">\\s*(.*?)\\s*</div>").matcher(block);
            if (!dm.find()) continue;
            String dateText = stripTags(dm.group(1)).trim();
            if (firstOut == null) {
                StringBuilder tmp = new StringBuilder();
                Pattern itemP0 = Pattern.compile("(?is)<div\\s+class=\"news-item\">\\s*<div\\s+class=\"news-content\">(.*?)</div>\\s*</div>");
                Matcher im0 = itemP0.matcher(block);
                while (im0.find()) {
                    String item = im0.group(1);
                    String title = null;
                    Matcher tm = Pattern.compile("(?is)<h2>.*?<a[^>]*>(.*?)</a>.*?</h2>").matcher(item);
                    if (tm.find()) title = stripTags(tm.group(1)).trim();
                    String desc = null;
                    Matcher pm = Pattern.compile("(?is)<p[^>]*>(.*?)</p>").matcher(item);
                    if (pm.find()) desc = stripTags(pm.group(1)).trim();
                    if (StrUtil.isBlank(title) && StrUtil.isBlank(desc)) continue;
                    if (!StrUtil.isBlank(title)) tmp.append(title).append('\n');
                    if (!StrUtil.isBlank(desc)) tmp.append(desc).append('\n');
                    tmp.append('\n');
                }
                firstOut = tmp.toString().replaceAll("\\n{3,}", "\n\n").trim();
            }
            if (!StrUtil.isBlank(dateText) && dateText.startsWith(todayPrefix)) {
                StringBuilder sb = new StringBuilder();
                Pattern itemP = Pattern.compile("(?is)<div\\s+class=\"news-item\">\\s*<div\\s+class=\"news-content\">(.*?)</div>\\s*</div>");
                Matcher im = itemP.matcher(block);
                while (im.find()) {
                    String item = im.group(1);
                    String title = null;
                    Matcher tm = Pattern.compile("(?is)<h2>.*?<a[^>]*>(.*?)</a>.*?</h2>").matcher(item);
                    if (tm.find()) title = stripTags(tm.group(1)).trim();
                    String desc = null;
                    Matcher pm = Pattern.compile("(?is)<p[^>]*>(.*?)</p>").matcher(item);
                    if (pm.find()) desc = stripTags(pm.group(1)).trim();
                    if (StrUtil.isBlank(title) && StrUtil.isBlank(desc)) continue;
                    if (!StrUtil.isBlank(title)) sb.append(title).append('\n');
                    if (!StrUtil.isBlank(desc)) sb.append(desc).append('\n');
                    sb.append('\n');
                }
                String out = sb.toString().replaceAll("\\n{3,}", "\n\n").trim();
                return StrUtil.isBlank(out) ? null : out;
            }
        }
        return StrUtil.isBlank(firstOut) ? null : firstOut;
    }

    /**
     * 去标签并保留换行，将 HTML 片段转换为纯文本
     */
    private String stripTags(String src) {
        if (src == null) return null;
        String pre = src
                .replaceAll("(?i)<br\\s*/?>", "\n")
                .replaceAll("(?i)</p>", "\n")
                .replaceAll("(?i)</li>", "\n")
                .replaceAll("(?i)</h[1-6]>", "\n");
        String text = pre.replaceAll("<[^>]+>", "");
        text = text.replaceAll("&nbsp;", " ");
        return text.replaceAll("\\s+", " ").replaceAll(" ?\n ?", "\n").trim();
    }

    /**
     * 提取页面第一块新闻日期标签
     */
    private String extractLatestDateLabelFromUrl(String url) throws Exception {
        String html = httpGet(url);
        if (StrUtil.isBlank(html)) return null;
        Pattern blockStart = Pattern.compile("(?is)<div\\s+class=\\\"news-list\\\">\\s*");
        Matcher m = blockStart.matcher(html);
        if (!m.find()) return null;
        int startIdx = m.start();
        int endIdx;
        if (m.find()) { endIdx = m.start(); } else { endIdx = html.length(); }
        String block = html.substring(startIdx, endIdx);
        Matcher dm = Pattern.compile("(?is)<div\\s+class=\\\"news-date\\\">\\s*(.*?)\\s*</div>").matcher(block);
        if (!dm.find()) return null;
        return stripTags(dm.group(1)).trim();
    }

    /**
     * 将长文本摘要为通知文案（截取首行或前100字）
     */
    private String summarizeForAlert(String content) {
        String firstLine = content.split("\n", 2)[0];
        if (firstLine.length() > 100) {
            return firstLine.substring(0, 100) + "…";
        }
        return firstLine;
    }

    /**
     * 推送给所有用户（与现有极光推送逻辑保持一致）
     */
    private void pushToAllUsers(String alert, Map<String, Object> extras, String title) {
        List<AppUser> users = appUserService.list(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getDelFlag, "N")
                .isNotNull(AppUser::getRegistrationId));
        int successCount = 0;
        int failCount = 0;
        for (AppUser user : users) {
            String rid = user.getRegistrationId();
            if (rid == null || rid.isBlank()) { continue; }
            JPushUtil.PushResult result = jPushUtil.pushNotificationToRegistrationId(rid, alert, extras, title);
            if (result.isSuccess()) {
                successCount++;
            } else {
                failCount++;
            }
        }
        log.info("每日AI资讯推送完成：成功 {} 条，失败 {} 条，共 {} 条", successCount, failCount, users.size());
    }

    /**
     * 推送给所有用户（按 uniqueId 去重），并为成功推送的用户落库用户级通知
     */
    private void pushToUsersDedup(String alert, Map<String, Object> extras, String title, String uniqueId, String content, String details) {
        List<AppUser> users = appUserService.list(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getDelFlag, "N")
                .isNotNull(AppUser::getRegistrationId));
        int successCount = 0;
        int skipCount = 0;
        int failCount = 0;
        for (AppUser user : users) {
            String rid = user.getRegistrationId();
            if (rid == null || rid.isBlank()) { continue; }
            // 用户ID为空的不推送、不保存
            if (user.getUserId() == null) { skipCount++; continue; }
            boolean alreadyHas = hasRecentNotification(user.getUserId(), uniqueId, "daily_ai_news", 10);
            if (alreadyHas) { skipCount++; continue; }
            Long nid = null;
            try {
                String aiSummary = summarizeForAlert(content);
                nid = notificationService.addUserNotificationReturnId(user.getUserId(), "每日AI资讯（" + uniqueId + ")", content, aiSummary, "https://ai-bot.cn/daily-ai-news/", "daily_ai_news", uniqueId, details);
            } catch (Exception ignored) {}
            Map<String, Object> perExtras = new HashMap<>();
            perExtras.put("type", "daily_ai_news");
            perExtras.put("date", uniqueId);
            if (nid != null) perExtras.put("notificationId", String.valueOf(nid));
            JPushUtil.PushResult result = jPushUtil.pushNotificationToRegistrationId(rid, alert, perExtras, title);
            if (result.isSuccess()) {
                successCount++;
            } else {
                failCount++;
            }
        }
        log.info("每日AI资讯推送完成：成功 {} 条，跳过 {} 条，失败 {} 条，共 {} 条", successCount, skipCount, failCount, users.size());
    }

    /**
     * 判断指定用户最近N条通知中是否已存在给定 uniqueId 与类型
     */
    private boolean hasRecentNotification(Long userId, String uniqueId, String type, int limit) {
        if (userId == null) return false;
        List<cn.stylefeng.guns.modular.time.entity.Notification> list = notificationService.lambdaQuery()
                .eq(cn.stylefeng.guns.modular.time.entity.Notification::getDelFlag, "N")
                .eq(cn.stylefeng.guns.modular.time.entity.Notification::getUserId, userId)
                .eq(cn.stylefeng.guns.modular.time.entity.Notification::getType, type)
                .orderByDesc(cn.stylefeng.guns.modular.time.entity.Notification::getCreateTime)
                .last("limit " + limit)
                .list();
        for (cn.stylefeng.guns.modular.time.entity.Notification n : list) {
            if (uniqueId != null && uniqueId.equals(n.getUniqueId())) return true;
        }
        return false;
    }

    /**
     * 构造详情 JSON（按段落拆分）
     */
    private String buildDetailsJsonFromItems(String dateLabel, java.util.List<NewsItem> items) {
        StringBuilder sb = new StringBuilder();
        sb.append('{').append("\"date\":\"").append(dateLabel).append("\",");
        sb.append("\"items\":[");
        if (items != null) {
            for (int i = 0; i < items.size(); i++) {
                NewsItem it = items.get(i);
                String title = it.title == null ? "" : it.title;
                String url = it.url == null ? "" : it.url;
                String desc = it.desc == null ? "" : it.desc;
                String source = it.source == null ? "" : it.source;
                sb.append('{')
                        .append("\"title\":\"").append(escapeJson(title)).append("\",")
                        .append("\"url\":\"").append(escapeJson(url)).append("\",")
                        .append("\"desc\":\"").append(escapeJson(desc)).append("\",")
                        .append("\"source\":\"").append(escapeJson(source)).append("\"}");
                if (i < items.size() - 1) sb.append(',');
            }
        }
        sb.append(']').append('}');
        return sb.toString();
    }

    private String escapeJson(String s) {
        return s.replace("\\", "\\\\").replace("\"", "\\\"");
    }

    private static class NewsItem {
        String title;
        String url;
        String desc;
        String source;
    }

    /**
     * 解析 HTML，抽取今日块内的新闻项（标题、链接、简介、来源）。未匹配今天时返回首块。
     */
    private java.util.List<NewsItem> extractTodayNewsItemsFromHtml(String html) {
        String todayPrefix = buildTodayLabel();
        Pattern blockStart = Pattern.compile("(?is)<div\\s+class=\\\"news-list\\\">\\s*");
        Matcher m = blockStart.matcher(html);
        java.util.List<String> blocks = new java.util.ArrayList<>();
        int lastStart = -1;
        while (m.find()) {
            if (lastStart != -1) {
                blocks.add(html.substring(lastStart, m.start()));
            }
            lastStart = m.start();
        }
        if (lastStart != -1) {
            blocks.add(html.substring(lastStart));
        }
        if (blocks.isEmpty()) return java.util.Collections.emptyList();
        java.util.List<NewsItem> firstItems = null;
        for (String block : blocks) {
            Matcher dm = Pattern.compile("(?is)<div\\s+class=\\\"news-date\\\">\\s*(.*?)\\s*</div>").matcher(block);
            if (!dm.find()) continue;
            String dateText = stripTags(dm.group(1)).trim();
            java.util.List<NewsItem> items = extractItemsFromBlock(block);
            if (firstItems == null) firstItems = items;
            if (!StrUtil.isBlank(dateText) && dateText.startsWith(todayPrefix)) {
                return items;
            }
        }
        return firstItems != null ? firstItems : java.util.Collections.emptyList();
    }

    /**
     * 解析单个 news-list 区块中的所有条目
     */
    private java.util.List<NewsItem> extractItemsFromBlock(String block) {
        java.util.List<NewsItem> list = new java.util.ArrayList<>();
        Pattern itemP = Pattern.compile("(?is)<div\\s+class=\\\"news-item\\\">\\s*<div\\s+class=\\\"news-content\\\">(.*?)</div>\\s*</div>");
        Matcher im = itemP.matcher(block);
        while (im.find()) {
            String item = im.group(1);
            NewsItem ni = new NewsItem();
            Matcher am = Pattern.compile("(?is)<h2>\\s*<a[^>]*href\\s*=\\s*\"([^\"]+)\"[^>]*>(.*?)</a>\\s*</h2>").matcher(item);
            if (am.find()) {
                ni.url = am.group(1).trim();
                ni.title = stripTags(am.group(2)).trim();
            }
            Matcher pm = Pattern.compile("(?is)<p[^>]*>(.*?)</p>").matcher(item);
            if (pm.find()) {
                String pInner = pm.group(1);
                ni.desc = stripTags(pInner).trim();
                Matcher srcM = Pattern.compile("来源：([^<]+)").matcher(pInner);
                if (srcM.find()) { ni.source = srcM.group(1).trim(); }
            }
            if (!StrUtil.isBlank(ni.title) || !StrUtil.isBlank(ni.desc) || !StrUtil.isBlank(ni.url)) {
                list.add(ni);
            }
        }
        return list;
    }

    /**
     * 从 URL 读取页面并返回今日新闻项
     */
    private java.util.List<NewsItem> extractTodayNewsItemsFromUrl(String url) throws Exception {
        String html = httpGet(url);
        if (StrUtil.isBlank(html)) return java.util.Collections.emptyList();
        return extractTodayNewsItemsFromHtml(html);
    }

    /**
     * 生成今日日期中文标签，例如：10月27
     */
    private String buildTodayLabel() {
        LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai"));
        return today.getMonthValue() + "月" + today.getDayOfMonth();
    }

    /**
     * 在纯文本中定位当日标题位置
     */
    private int indexOfToday(String text, String todayLabel) {
        // 标题可能形如：10月27·周一 / 10月27 / 10月27日
        Pattern p = Pattern.compile(Pattern.quote(todayLabel) + "(?:[·\\.\\s]*[\\u4e00-\\u9fa5]*)?");
        Matcher m = p.matcher(text);
        return m.find() ? m.start() : -1;
    }

    /**
     * 寻找下一天的日期标题位置，用于截取当日区块
     */
    private int indexOfNextDate(String text, int fromIndex) {
        Pattern p = Pattern.compile("(\\d{1,2}月\\d{1,2})(?:[·\\.\\s]*[\\u4e00-\\u9fa5]*)?");
        Matcher m = p.matcher(text);
        if (!m.find(fromIndex)) return -1;
        return m.start();
    }

    /**
     * 简单HTTP GET，返回UTF-8文本
     */
    private String httpGet(String link) throws Exception {
        HttpURLConnection conn = null;
        try {
            URL url = new URL(link);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            try (InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                byte[] buf = new byte[4096]; int len;
                while ((len = is.read(buf)) != -1) { bos.write(buf, 0, len); }
                return bos.toString(StandardCharsets.UTF_8);
            }
        } finally {
            if (conn != null) conn.disconnect();
        }
    }
}
